/*-
 * Public platform independent Near Field Communication (NFC) library examples
 * 
 * Copyright (C) 2009, Roel Verdult
 * Copyright (C) 2010, Romuald Conty
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *  1) Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer. 
 *  2 )Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Note that this license only applies on the examples, NFC library itself is under LGPL
 *
 */

/*MIFARE Ultralight dump/restore tool */

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>

#include <string.h>
#include <ctype.h>

#include <host/nfc.h>

#include "nfc-utils.h"
#include "mifare.h"

static nfc_device_t *pnd;
static nfc_target_t nt;
static mifare_param mp;
static mifareul_tag mtDump;
static uint32_t uiBlocks = 0xF;

static const nfc_modulation_t nmMifare = {
	NMT_ISO14443A,
	NBR_106,
};

static void print_success_or_failure(bool bFailure,
				     uint32_t *uiCounter)
{
	printf("%c", (bFailure) ? 'x' : '.');
	if (uiCounter)
		*uiCounter += (bFailure) ? 0 : 1;
}

static bool read_card(void)
{
	uint32_t page;
	bool bFailure = false;
	uint32_t uiReadedPages = 0;
	
	printf ("Reading %d pages |", uiBlocks + 1);
	
	for (page = 0; page <= uiBlocks; page += 4) {
		/* Try to read out the data block */
		if (nfc_initiator_mifare_cmd(pnd, MC_READ, page, &mp)) {
			memcpy(mtDump.amb[page / 4].mbd.abtData, mp.mpd.abtData, 16);
		} else {
			bFailure = true;
			break;
		}
		
		print_success_or_failure(bFailure, &uiReadedPages);
		print_success_or_failure(bFailure, &uiReadedPages);
		print_success_or_failure(bFailure, &uiReadedPages);
		print_success_or_failure(bFailure, &uiReadedPages);
	}
	printf("|\n");
	printf("Done, %d of %d pages readed.\n", uiReadedPages, uiBlocks + 1);
	fflush(stdout);
	
	return (!bFailure);
}

static bool write_card(void)
{
	uint32_t uiBlock = 0;
	bool bFailure = false;
	uint32_t uiWritenPages = 0;
	uint32_t uiSkippedPages;
	
	char buffer[BUFSIZ];
	bool write_otp;
	bool write_lock;
	
	int page;
	printf("Write OTP bytes ? [yN] ");
	if (!fgets(buffer, BUFSIZ, stdin)) {
		ERR("Unable to read standard input.");
	}
	write_otp = ((buffer[0] == 'y') || (buffer[0] == 'Y'));
	printf("Write Lock bytes ? [yN] ");
	if (!fgets (buffer, BUFSIZ, stdin)) {
		ERR("Unable to read standard input.");
	}
	write_lock = ((buffer[0] == 'y') || (buffer[0] == 'Y'));
	
	printf("Writing %d pages |", uiBlocks + 1);
	/* We need to skip 2 first pages. */
	printf("ss");
	uiSkippedPages = 2;
	
	for (page = 0x2; page <= 0xF; page++) {
		if ((page==0x2) && (!write_lock)) {
			printf("s");
			uiSkippedPages++;
			continue;
		}
		if ((page==0x3) && (!write_otp)) {
			printf("s");
			uiSkippedPages++;
			continue;
		}
		/* Show if the readout went well */
		if (bFailure) {
			/* When a failure occured we need to redo the
			 * anti-collision
			 */
			if (!nfc_initiator_select_passive_target(pnd, nmMifare,
								 NULL, 0, &nt)) {
				ERR("tag was removed");
				return false;
			}
			bFailure = false;
		}
		/* For the Mifare Ultralight, this write command can be
		 * used in compatibility mode, which only actually writes
		 * the first page (4 bytes). The Ultralight-specific Write
		 * command only writes one page at a time.
		 */
		uiBlock = page / 4;
		memcpy(mp.mpd.abtData,
		       mtDump.amb[uiBlock].mbd.abtData + ((page % 4) * 4), 16);
		if (!nfc_initiator_mifare_cmd(pnd, MC_WRITE, page, &mp))
			bFailure = true;
		
		print_success_or_failure(bFailure, &uiWritenPages);
	}
	printf("|\n");
	printf("Done, %d of %d pages written (%d pages skipped).\n",
	       uiWritenPages, uiBlocks + 1, uiSkippedPages);
	
	return true;
}

int main(int argc, const char *argv[])
{
	bool bReadAction;
	FILE *pfDump;
	size_t szPos;
	
	if (argc < 3) {
		printf("\n");
		printf("%s r|w <dump.mfd>\n", argv[0]);
		printf("\n");
		printf("r|w         - Perform read from or write to card\n");
		printf("<dump.mfd>  - MiFare Dump (MFD) used to write (card to MFD) or (MFD to card)\n");
		printf("\n");
		return 1;
	}
	
	DBG("\nChecking arguments and settings\n");
	
	bReadAction = tolower((int) ((unsigned char) *(argv[1])) == 'r');
	
	if (bReadAction) {
		memset(&mtDump, 0x00, sizeof (mtDump));
	} else {
		pfDump = fopen(argv[2], "rb");
		
		if (pfDump == NULL) {
			ERR("Could not open dump file: %s\n", argv[2]);
			return 1;
		}
		
		if (fread(&mtDump, 1, sizeof (mtDump), pfDump) != sizeof (mtDump)) {
			ERR("Could not read from dump file: %s\n", argv[2]);
			fclose(pfDump);
			return 1;
		}
		fclose (pfDump);
	}
	DBG("Successfully opened the dump file\n");
	
	/* Try to open the NFC device */
	pnd = nfc_connect(NULL);
	if (pnd == NULL) {
		ERR("Error connecting NFC device\n");
		return 1;
	}
	
	nfc_initiator_init(pnd);
	
	/* Let the device only try once to find a tag */
	if (!nfc_configure(pnd, NDO_INFINITE_SELECT, false)) {
		nfc_perror(pnd, "nfc_configure");
		exit(EXIT_FAILURE);
	}
	
	printf("Connected to NFC device: %s\n", pnd->acName);
	
	/* Try to find a MIFARE Ultralight tag */
	if (!nfc_initiator_select_passive_target(pnd, nmMifare, NULL, 0, &nt)) {
		ERR("no tag was found\n");
		nfc_disconnect(pnd);
		return 1;
	}
	/* Test if we are dealing with a MIFARE compatible tag */
	
	if (nt.nti.nai.abtAtqa[1] != 0x44) {
		ERR("tag is not a MIFARE Ultralight card\n");
		nfc_disconnect(pnd);
		return EXIT_FAILURE;
	}
	/* Get the info from the current tag */
	printf("Found MIFARE Ultralight card with UID: ");
	for (szPos = 0; szPos < nt.nti.nai.szUidLen; szPos++) {
		printf("%02x", nt.nti.nai.abtUid[szPos]);
	}
	printf("\n");
	
	if (bReadAction) {
		if (read_card()) {
			printf("Writing data to file: %s ... ", argv[2]);
			fflush(stdout);
			pfDump = fopen(argv[2], "wb");
			if (pfDump == NULL) {
				printf("Could not open file: %s\n", argv[2]);
				return EXIT_FAILURE;
			}
			if (fwrite(&mtDump, 1, sizeof (mtDump), pfDump) != sizeof (mtDump)) {
				printf("Could not write to file: %s\n", argv[2]);
				return EXIT_FAILURE;
			}
			fclose(pfDump);
			printf("Done.\n");
		}
	} else {
		write_card();
	}
	
	nfc_disconnect(pnd);
	return EXIT_SUCCESS;
}
